import json
import os

from beeos import Process, Pin
from log import Log
from rtc import RTCHelper

log = Log(tag='device')
OFF = 'off'
BACKWARD = 'backward'
FORWARD = 'forward'

M1 = 'motor1'
M2 = 'motor2'


class Motor:
    def __init__(self, a, b, fo, bo, limit):
        self.a = Pin(a, Pin.OUT)
        self.b = Pin(b, Pin.OUT)
        self.fo = Pin(fo, Pin.OUT)
        self.bo = Pin(bo, Pin.OUT)
        self.limit = Pin(limit, Pin.IN)
        self.state = OFF
        self.off()

    def forward(self):
        self.state = FORWARD
        self.fo.value(0)
        self.bo.value(1)
        self.a.value(0)
        self.b.value(1)

    def backward(self):
        self.state = BACKWARD
        self.fo.value(1)
        self.bo.value(0)
        self.a.value(1)
        self.b.value(0)

    def off(self):
        self.state = OFF
        self.fo.value(1)
        self.bo.value(1)
        self.a.value(0)
        self.b.value(0)

    def auto_check(self):
        if self.limit.value() == 0:
            old_state = self.state
            self.off()
            return old_state != OFF
        return False

    def set_state(self, state):
        if state == FORWARD:
            self.forward()
        elif state == BACKWARD:
            self.backward()
        elif state == OFF:
            self.off()
        else:
            log.warn('IVL_STATE:%s' % state)


def nop(_):
    pass


WIND_AD = 39
RAIN_IN = 36

M1_OF = 19
M1_OB = 21
M2_OF = 23
M2_OB = 22

M1_IF = 26
M1_IB = 25
M2_IF = 33
M2_IB = 32

M1_LIM = 34
M2_LIM = 35

M1_A = 16
M1_B = 2
M2_A = 18
M2_B = 17

SHUT_IN = 27
RECO_IN = 13

MAX_TASKS = 10

_TASK_DIR = '/_task'


class DeviceTaskContext:
    @staticmethod
    def _init_dir():
        try:
            os.stat(_TASK_DIR)
        except Exception as e:
            log.warn('Create Task Dir', e)
            os.mkdir(_TASK_DIR)

    def __init__(self, on_task_run):
        self.tasks = []
        DeviceTaskContext._init_dir()
        self.on_task_run = on_task_run

    def load_tasks(self):
        files = os.listdir(_TASK_DIR)
        if not files:
            return
        for file in files:
            time = RTCHelper.parse(file)
            if not time:
                log.warn('INV,TASK_FILE:' + file)
                continue
            with open(_TASK_DIR + '/' + file) as f:
                content = f.read()
            status = json.loads(content)
            task = DeviceTask(time, status)
            self.tasks.append(task)

    def remove_task(self, task):
        file = None
        try:
            self.tasks.remove(task)
            file = _TASK_DIR + '/' + RTCHelper.format(task.time)
            os.remove(file)
        except Exception as e:
            log.error('RemoveTaskError:[%s]' % file, e)

    def run_tasks(self):
        done = []
        for task in self.tasks:
            try:
                if task.try_run_task():
                    done.append(task)
                    self.on_task_run(task)
            except Exception as e:
                log.error('Error on run task:' + RTCHelper.format(task.time), e)
                done.append(task)
        for task in done:
            self.remove_task(task)

    def add_task(self, task):
        if len(self.tasks) > MAX_TASKS:
            raise Exception('TaskListTooBig')
        try:
            log.info('Adding Task:[%s]' % RTCHelper.format(task.time))
            DeviceTaskContext._save(task)
            self.tasks.append(task)
        except Exception as e:
            log.error('E:Save Task', e)
            raise e

    @staticmethod
    def _save(task):
        file = _TASK_DIR + '/' + RTCHelper.format(task.time)
        content = json.dumps(task.status)
        with open(file, 'w') as f:
            f.write(content)


class DeviceProcess(Process):
    def __init__(self):
        self.motor1 = Motor(M1_A, M1_B, M1_OF, M1_OB, M1_LIM)
        self.motor2 = Motor(M2_A, M2_B, M2_OF, M2_OB, M2_LIM)
        self.on_change = nop
        self.is_shutdown = False
        self.task_context = DeviceTaskContext(lambda task: self.on_task_run(task))
        self.rtc_ready = False

    def set_rtc_ready(self, ready):
        self.rtc_ready = ready

    def loop(self):
        changed = self.motor1.auto_check()
        changed |= self.motor2.auto_check()
        if changed:
            self.on_change(self.get_status())
        if self.rtc_ready:
            try:
                self.task_context.run_tasks()
            except Exception as e:
                log.error('run task', e)
        return False

    def get_status(self):
        return {
            M1: self.motor1.state,
            M2: self.motor2.state,
        }

    def on_task_run(self, task):
        self.on_change(task.status)

    def set_state(self, status):
        if self.is_shutdown:
            return
        m1 = status[M1]
        m2 = status[M2]
        log.debug('SetState,m1=%s,m2=%s' % (m1, m2))
        self.motor1.set_state(m1)
        self.motor2.set_state(m2)

    def setup(self):
        self.motor1.off()
        self.motor2.off()
        self.task_context.load_tasks()

    def shutdown(self):
        self.motor1.off()
        self.motor2.off()
        self.is_shutdown = True

    def recover(self):
        self.is_shutdown = False


device_ctl_process = DeviceProcess()


class KeyControllerProcess(Process):
    def __init__(self, on_change, device_ctl=device_ctl_process, on_long_press=nop):
        self.m1f = self.build_key(M1_IF)
        self.m1b = self.build_key(M1_IB)
        self.m2f = self.build_key(M2_IF)
        self.m2b = self.build_key(M2_IB)
        self.shut = self.build_key(SHUT_IN)
        self.reco = self.build_key(RECO_IN)
        self.device_ctl = device_ctl
        self.reco_ct = 0
        self.on_long_press = on_long_press
        self.on_change = on_change

    def build_key(self, pin_no):
        pin = Pin(pin_no, Pin.IN)
        pin.irq(trigger=Pin.IRQ_FALLING, handler=lambda pin: self._on_key_down(pin_no, pin))
        return pin

    def _on_key_down(self, pin_no, _):
        need_rep = False
        if pin_no == M1_IF:
            self.device_ctl.motor1.forward()
            need_rep = True
        elif pin_no == M1_IB:
            self.device_ctl.motor1.backward()
            need_rep = True
        elif pin_no == M2_IF:
            self.device_ctl.motor2.forward()
            need_rep = True
        elif pin_no == M2_IB:
            self.device_ctl.motor2.backward()
            need_rep = True
        elif pin_no == SHUT_IN:
            self.device_ctl.shutdown()
            need_rep = True
        elif pin_no == RECO_IN:
            self.device_ctl.recover()
        if need_rep:
            self.on_change()

    def setup(self):
        pass

    def loop(self):
        if self.reco.value() == 0:
            self.reco_ct += 1
        else:
            self.reco_ct = 0
        if self.reco_ct > 50:
            self.on_long_press()
        return False


class DeviceTask:
    def __init__(self, time, status):
        self.time = time
        self.status = status

    def try_run_task(self):
        now = RTCHelper.current_time_tuple6()
        if DeviceTask._is_expired(now, self.time):
            log.info('RunTask[%s] now=[%s]' % (RTCHelper.format(self.time), RTCHelper.format(now)))
            device_ctl_process.set_state(self.status)
            return True
        return False

    @staticmethod
    def _is_expired(now, time):
        for i in range(len(now)):
            a = now[i]
            b = time[i]
            if a > b:
                return True
            elif a < b:
                return False
            else:
                continue
        return False
